package com.yjck.ijpay.utils;

import cn.binarywang.wx.miniapp.api.WxMaService;
import cn.binarywang.wx.miniapp.bean.WxMaJscode2SessionResult;
import cn.binarywang.wx.miniapp.bean.WxMaPhoneNumberInfo;
import cn.hutool.core.io.FileUtil;
import cn.hutool.core.io.file.FileWriter;
import cn.hutool.core.util.StrUtil;
import cn.hutool.json.JSONArray;
import cn.hutool.json.JSONUtil;
import com.alibaba.fastjson.JSONObject;
import com.ijpay.core.IJPayHttpResponse;
import com.ijpay.core.enums.RequestMethod;
import com.ijpay.core.kit.AesUtil;
import com.ijpay.core.kit.HttpKit;
import com.ijpay.core.kit.PayKit;
import com.ijpay.core.kit.WxPayKit;
import com.ijpay.core.utils.DateTimeZoneUtil;
import com.ijpay.wxpay.WxPayApi;
import com.ijpay.wxpay.enums.WxApiType;
import com.ijpay.wxpay.enums.WxDomain;
import com.ijpay.wxpay.model.v3.*;
import com.yjck.ijpay.config.CommonConstant;
import com.yjck.ijpay.config.pay.RiskFund;
import com.yjck.ijpay.config.pay.TimeRange;
import com.yjck.ijpay.config.pay.WxPayCallback;
import com.yjck.ijpay.config.pay.WxPayScore;
import com.yjck.ijpay.config.wxLogin.WxMaConfiguration;
import com.yjck.ijpay.helper.ConfigHelper;
import com.yjck.ijpay.pojo.BaseResponse;
import com.yjck.ijpay.pojo.XhMemberPay;
import lombok.extern.slf4j.Slf4j;
import me.chanjar.weixin.common.error.WxErrorException;
import okhttp3.Response;

import javax.servlet.http.HttpServletRequest;
import java.io.ByteArrayInputStream;
import java.io.IOException;
import java.math.BigDecimal;
import java.nio.charset.StandardCharsets;
import java.security.cert.X509Certificate;
import java.util.*;

/**
 * 微信相关工具类
 *
 * @author lyz
 * @version [1.0, 2021-04-28]
 */
@Slf4j
public class WeChatUtil {
    // weChat 通用配置
    public static String APP_ID = ConfigHelper.getConfig().getString("weChat.appId");
    public static String SECRET = ConfigHelper.getConfig().getString("weChat.secret");
    // weChat v3支付配置
    public static String KEY_PATH = ConfigHelper.getConfig().getString("weChat.v3.keyPath");
    public static String CERT_PATH = ConfigHelper.getConfig().getString("weChat.v3.certPath");
    public static String PLATFORM_CERT_PATH = ConfigHelper.getConfig().getString("weChat.v3.platformCertPath");
    public static String MCH_ID = ConfigHelper.getConfig().getString("weChat.v3.mchId");
    public static String API_KEY3 = ConfigHelper.getConfig().getString("weChat.v3.apiKey3");
    public static String DOMAIN = ConfigHelper.getConfig().getString("weChat.v3.domain");
    // weChat 小程序接口
    // 获取小程序全局唯一后台接口调用凭据
    public static String ACCESS_TOKEN_URL = ConfigHelper.getConfig().getString("weChat.weChatUrl.accessTokenUrl");
    // 小程序发送订阅消息
    public static String SUBSCRIBE_MESSAGE_URL = ConfigHelper.getConfig().getString("weChat.weChatUrl.subscribeMessageUrl");

    /**
     * 获取小程序全局唯一后台接口调用凭据
     *
     * @return java.lang.String
     * @author lyz
     * @version [1.0, 2021-04-28]
     */
    public static Map<String, Object> getAccessToken() throws IOException {
        StringBuffer str = new StringBuffer(ACCESS_TOKEN_URL);
        String strUrl = str.append("grant_type=")
                .append("client_credential")
                .append("&appid=")
                .append(APP_ID)
                .append("&secret=")
                .append(SECRET)
                .toString();
        Response res = HttpRequestUtil.get(strUrl);
        String result = res.body().string();
        Map<String, Object> map = JsonUtil.toObject(result, HashMap.class);
        return map;
    }


    public static String serialNo;

    private final static int OK = 200;

    /**
     * @param openId
     * @param description
     * @param jsonObject
     * @param oNo
     * @param multiply
     * @return com.xingheng.common.pojo.BaseResponse
     * @Description: 微信支付
     * @author lyz
     * @version [1.0, 2021-04-28]
     */
    public static BaseResponse weChatPay(String openId, String description, JSONObject jsonObject, String oNo, BigDecimal multiply) {
        try {
            String timeExpire = DateTimeZoneUtil.dateToTimeZone(System.currentTimeMillis() + 1000 * 60 * 3);
            UnifiedOrderModel unifiedOrderModel = new UnifiedOrderModel()
                    .setAppid(APP_ID)
                    .setMchid(MCH_ID)
                    .setDescription(description)
                    .setOut_trade_no(oNo)
                    .setTime_expire(timeExpire)
                    .setAttach(jsonObject.toJSONString())
                    .setNotify_url(DOMAIN.concat("/api/payNotify"))
                    .setAmount(new Amount().setTotal(multiply.intValue()))
                    .setPayer(new Payer().setOpenid(openId));

            log.info("统一下单参数 {}", JSONUtil.toJsonStr(unifiedOrderModel));
            IJPayHttpResponse response = WxPayApi.v3(
                    RequestMethod.POST,
                    WxDomain.CHINA.toString(),
                    WxApiType.JS_API_PAY.toString(),
                    MCH_ID,
                    getSerialNumber(),
                    null,
                    KEY_PATH,
                    JSONUtil.toJsonStr(unifiedOrderModel)
            );
            log.info("统一下单响应 {}", response);

            if (response.getStatus() == OK) {
                String body = response.getBody();
                cn.hutool.json.JSONObject jsonObject1 = JSONUtil.parseObj(body);
                String prepayId = jsonObject1.getStr("prepay_id");
                Map<String, String> map = WxPayKit.jsApiCreateSign(APP_ID, prepayId, KEY_PATH);
                map.put("orderNo", oNo);
                log.info("唤起支付参数:{}", map);
                return BaseResponse.success(JSONObject.parseObject(JSONUtil.toJsonStr(map)));
            }
            return BaseResponse.error("支付失败,统一下单异常");
        } catch (Exception e) {
            e.printStackTrace();
            return BaseResponse.error();
        }
    }

    /**
     * @return com.xingheng.common.pojo.BaseResponse
     * @Description: 微信支付订单号查询
     * @author lyz
     * @version [1.0, 2021-04-28]
     */
    public static BaseResponse weChatTransactions(String transactionsId) {
        try {
            IJPayHttpResponse response = WxPayApi.v3(
                    RequestMethod.GET,
                    WxDomain.CHINA.toString(),
                    String.format(WxApiType.ORDER_QUERY_BY_NO.toString() + "?mchid=" + MCH_ID, transactionsId),
//                    "/v3/pay/transactions/id/" + transactionsId + "?mchid=" + MCH_ID,
                    MCH_ID,
                    getSerialNumber(),
                    null,
                    KEY_PATH,
                    ""
            );
            log.info("查询订单API返回体 = {}", response);

            if (response.getStatus() == OK) {
                String body = response.getBody();
                cn.hutool.json.JSONObject jsonObject1 = JSONUtil.parseObj(body);
                String prepayId = jsonObject1.getStr("prepay_id");
                Map<String, String> map = WxPayKit.jsApiCreateSign(APP_ID, prepayId, KEY_PATH);
                log.info("唤起支付参数:{}", map);
                return BaseResponse.success(JSONObject.parseObject(JSONUtil.toJsonStr(map)));
            }
            return BaseResponse.error("支付失败,统一下单异常");
        } catch (Exception e) {
            e.printStackTrace();
            return BaseResponse.error();
        }
    }

    /**
     * @return com.xingheng.common.pojo.BaseResponse
     * @Description: 微信支付订单号查询
     * @author lyz
     * @version [1.0, 2021-04-28]
     */
    public static WxPayCallback weChatTransactionsOutTradeNo(String outTradeNo) {
        WxPayCallback wxPayCallback = new WxPayCallback();
        try {
            IJPayHttpResponse response = WxPayApi.v3(
                    RequestMethod.GET,
                    WxDomain.CHINA.toString(),
                    String.format(WxApiType.ORDER_QUERY_BY_NO.toString() + "?mchid=" + MCH_ID, outTradeNo),
                    MCH_ID,
                    getSerialNumber(),
                    null,
                    KEY_PATH,
                    ""
            );
            log.info("查询订单API返回体 = {}", response);

            if (response.getStatus() == OK) {
                String body = response.getBody();
                cn.hutool.json.JSONObject jsonObject1 = JSONUtil.parseObj(body);
                wxPayCallback = JSONObject.parseObject(JSONUtil.toJsonStr(jsonObject1), WxPayCallback.class);
                return wxPayCallback;
            }
            return wxPayCallback;
        } catch (Exception e) {
            e.printStackTrace();
            return wxPayCallback;
        }
    }

    /**
     * @return java.lang.String
     * @Description: 效验证书
     * @author lyz
     * @version [1.0, 2021-04-27]
     */
    public static String getSerialNumber() {
        if (StrUtil.isEmpty(serialNo)) {
            // 获取证书序列号
            X509Certificate certificate = PayKit.getCertificate(FileUtil.getInputStream(CERT_PATH));
            serialNo = certificate.getSerialNumber().toString(16).toUpperCase();

//            System.out.println("输出证书信息:\n" + certificate.toString());
//            // 输出关键信息，截取部分并进行标记
//            System.out.println("证书序列号:" + certificate.getSerialNumber().toString(16));
//            System.out.println("版本号:" + certificate.getVersion());
//            System.out.println("签发者：" + certificate.getIssuerDN());
//            System.out.println("有效起始日期：" + certificate.getNotBefore());
//            System.out.println("有效终止日期：" + certificate.getNotAfter());
//            System.out.println("主体名：" + certificate.getSubjectDN());
//            System.out.println("签名算法：" + certificate.getSigAlgName());
//            System.out.println("签名：" + certificate.getSignature().toString());
        }
//        System.out.println("serialNo:" + serialNo);
        return serialNo;
    }

    /**
     * @return java.lang.String
     * @Description: 获取V3平台证书
     * @author lyz
     * @version [1.0, 2021-04-28]
     */
    public static String getV3Cred() {
        // 获取平台证书列表
        try {
            IJPayHttpResponse response = WxPayApi.v3(
                    RequestMethod.GET,
                    WxDomain.CHINA.toString(),
                    WxApiType.GET_CERTIFICATES.toString(),
                    MCH_ID,
                    getSerialNumber(),
                    null,
                    KEY_PATH,
                    ""
            );

            String timestamp = response.getHeader("Wechatpay-Timestamp");
            String nonceStr = response.getHeader("Wechatpay-Nonce");
            String serialNumber = response.getHeader("Wechatpay-Serial");
            String signature = response.getHeader("Wechatpay-Signature");

            String body = response.getBody();
            int status = response.getStatus();

            log.info("serialNumber: {}", serialNumber);
            log.info("status: {}", status);
            log.info("body: {}", body);
            int isOk = 200;
            if (status == OK) {
                cn.hutool.json.JSONObject jsonObject = JSONUtil.parseObj(body);
                JSONArray dataArray = jsonObject.getJSONArray("data");
                // 默认认为只有一个平台证书
                cn.hutool.json.JSONObject encryptObject = dataArray.getJSONObject(0);
                cn.hutool.json.JSONObject encryptCertificate = encryptObject.getJSONObject("encrypt_certificate");
                String associatedData = encryptCertificate.getStr("associated_data");
                String cipherText = encryptCertificate.getStr("ciphertext");
                String nonce = encryptCertificate.getStr("nonce");
                String serialNo = encryptObject.getStr("serial_no");
                final String platSerialNo = savePlatformCert(associatedData, nonce, cipherText, PLATFORM_CERT_PATH);
//                log.info("平台证书序列号: {} serialNo: {}", platSerialNo, serialNo);
            }
            // 根据证书序列号查询对应的证书来验证签名结果
            boolean verifySignature = WxPayKit.verifySignature(response, PLATFORM_CERT_PATH);
//            System.out.println("verifySignature:" + verifySignature);
            return body;
        } catch (Exception e) {
            e.printStackTrace();
            return null;
        }
    }

    /**
     * @param associatedData
     * @param nonce
     * @param cipherText
     * @param CERT_PATH
     * @return java.lang.String
     * @Description: 解密获取回调数据
     * @author lyz
     * @version [1.0, 2021-04-27]
     */
    public static String savePlatformCert(String associatedData, String nonce, String cipherText, String CERT_PATH) {
        try {
            AesUtil aesUtil = new AesUtil(API_KEY3.getBytes(StandardCharsets.UTF_8));
            // 平台证书密文解密
            // encrypt_certificate 中的  associated_data nonce  ciphertext
            String publicKey = aesUtil.decryptToString(
                    associatedData.getBytes(StandardCharsets.UTF_8),
                    nonce.getBytes(StandardCharsets.UTF_8),
                    cipherText
            );
            // 保存证书
            FileWriter writer = new FileWriter(CERT_PATH);
            writer.write(publicKey);
            // 获取平台证书序列号
            X509Certificate certificate = PayKit.getCertificate(new ByteArrayInputStream(publicKey.getBytes()));
            return certificate.getSerialNumber().toString(16).toUpperCase();
        } catch (Exception e) {
            e.printStackTrace();
            return e.getMessage();
        }
    }

    /**
     * @return java.lang.String
     * @Description: 申请退款
     * @author lyz
     * @version [1.0, 2021-04-28]
     */
    public static String getWxRefund(XhMemberPay memberPay) {
        // 商户订单号
        String outTradeNo = memberPay.getOutTradeNo();
        String transactionId = memberPay.getTransactionId();
        // 微信端 最终接收的支付金额
        Integer payerTotal = memberPay.getPayerTotal().intValue();
        // 金额支付渠道
        String payChannel = memberPay.getPayChannel();

        try {
            String outRefundNo = PayKit.generateStr();
            log.info("商户退款单号: {}", outRefundNo);

            List<RefundGoodsDetail> list = new ArrayList<>();
            RefundGoodsDetail refundGoodsDetail = new RefundGoodsDetail()
                    .setMerchant_goods_id(outRefundNo)
                    .setGoods_name("押金退回")
                    .setUnit_price(payerTotal)
                    .setRefund_amount(payerTotal)
                    .setRefund_quantity(CommonConstant.ONE);
            list.add(refundGoodsDetail);

            RefundModel refundModel = new RefundModel()
                    .setTransaction_id(transactionId)
                    .setOut_refund_no(outRefundNo)
                    .setReason("押金退回")
                    .setNotify_url(DOMAIN.concat("/api/refundNotify"))
                    .setAmount(new RefundAmount().setRefund(payerTotal).setTotal(payerTotal).setCurrency("CNY"))
                    .setGoods_detail(list)
                    .setFunds_account("AVAILABLE");

            if (StrUtil.isNotEmpty(outTradeNo)) {
                refundModel.setOut_trade_no(outTradeNo);
            }
            log.info("退款参数 {}", JSONUtil.toJsonStr(refundModel));
            IJPayHttpResponse response = WxPayApi.v3(
                    RequestMethod.POST,
                    WxDomain.CHINA.toString(),
                    WxApiType.DOMESTIC_REFUNDS.toString(),
                    MCH_ID,
                    getSerialNumber(),
                    null,
                    KEY_PATH,
                    JSONUtil.toJsonStr(refundModel)
            );
            // 根据证书序列号查询对应的证书来验证签名结果
            boolean verifySignature = WxPayKit.verifySignature(response, PLATFORM_CERT_PATH);
            log.info("verifySignature: {}", verifySignature);
            log.info("退款响应 {}", response);

            if (verifySignature) {
                return response.getBody();
            }
        } catch (Exception e) {
            e.printStackTrace();
            return e.getMessage();
        }
        return null;
    }

    public static String WeChatNotify(HttpServletRequest request) throws Exception {
        String timestamp = request.getHeader("Wechatpay-Timestamp");
        String nonce = request.getHeader("Wechatpay-Nonce");
        String serialNo = request.getHeader("Wechatpay-Serial");
        String signature = request.getHeader("Wechatpay-Signature");

        log.info("timestamp:{} nonce:{} serialNo:{} signature:{}", timestamp, nonce, serialNo, signature);
        String result = HttpKit.readData(request);
        log.info("支付通知密文 {}", result);

        // 需要通过证书序列号查找对应的证书，verifyNotify 中有验证证书的序列号
        String plainText = WxPayKit.verifyNotify(serialNo, result, signature, nonce, timestamp,
                API_KEY3, PLATFORM_CERT_PATH);

        log.info("支付通知明文 {}", plainText);
        return plainText;
    }


    /**
     * 登陆接口
     */
    public static String login(String code) {
        final WxMaService wxService = WxMaConfiguration.getMaService();
        try {
            WxMaJscode2SessionResult session = wxService.getUserService().getSessionInfo(code);
            String openid = session.getOpenid();
            return openid;
        } catch (WxErrorException e) {
            e.printStackTrace();
        }
        return null;
    }

    /**
     * <pre>
     * 获取用户绑定手机号信息
     * </pre>
     */
    public static String phone(String code, String encryptedData, String iv) {
        final WxMaService wxService = WxMaConfiguration.getMaService();
        try {
            WxMaJscode2SessionResult session = wxService.getUserService().getSessionInfo(code);
            String sessionKey = session.getSessionKey();
            // 解密
            WxMaPhoneNumberInfo phoneNoInfo = wxService.getUserService().getPhoneNoInfo(sessionKey, encryptedData, iv);
            String purePhoneNumber = phoneNoInfo.getPurePhoneNumber();
            return purePhoneNumber;
        } catch (WxErrorException e) {
            e.printStackTrace();
        }
        return null;
    }

    /**
     * @param openId
     * @return java.lang.String
     * @Description: 判断用户是否开启支付分
     * @author lyz
     * @version [1.0, 2021-05-06]
     */
    public static String payScoreUserServiceState(String openId) {
        try {
            Map<String, String> params = new HashMap<>();
            params.put("service_id", "500001");
            params.put("appid", APP_ID);
            params.put("openid", openId);

            IJPayHttpResponse result = WxPayApi.v3(
                    RequestMethod.GET,
                    WxDomain.CHINA.toString(),
                    WxApiType.PAY_SCORE_USER_SERVICE_STATE.toString(),
                    MCH_ID,
                    getSerialNumber(),
                    null,
                    KEY_PATH,
                    params
            );
            System.out.println(result);
            return JSONUtil.toJsonStr(result);
        } catch (Exception e) {
            e.printStackTrace();
            return e.getMessage();
        }
    }

    /**
     * @return void
     * @Description: 微信支付分
     * @author lyz
     * @version [1.0, 2021-05-06]
     */
    public static void payScore() {
        try {
            WxPayScore wxPayScore = new WxPayScore();
            wxPayScore.setOutOrderNo("YJ-202104301740522825075");
            wxPayScore.setAppid(APP_ID);
            wxPayScore.setServiceId("500001");
            wxPayScore.setServiceIntroduction("城满电换电");
            TimeRange timeRange = new TimeRange();
            timeRange.setStartTime(DateUtil.date2string(new Date(), DateUtil.yyyyMMddHHmmss));
            wxPayScore.setTimeRange(timeRange);
            RiskFund riskFund = new RiskFund();
            riskFund.setName("DEPOSIT");
            riskFund.setAmount(500000L);
            wxPayScore.setRiskFund(riskFund);
            wxPayScore.setNotifyUrl(DOMAIN.concat("/api/payScoreNotify"));
            wxPayScore.setOpenid("oEPTw4jBVbMT4G1tt9z7m6CLIMsg");
            wxPayScore.setNeedUserConfirm(true);
            log.info("微信支付分参数 {}", JSONUtil.toJsonStr(wxPayScore));

            IJPayHttpResponse response = WxPayApi.v3(
                    RequestMethod.POST,
                    WxDomain.CHINA.toString(),
                    WxApiType.PAY_SCORE_SERVICE_ORDER.toString(),
                    MCH_ID,
                    getSerialNumber(),
                    null,
                    KEY_PATH,
                    JSONUtil.toJsonStr(wxPayScore)
            );
            log.info("微信支付分参数 {}", response);
        } catch (Exception e) {
            e.printStackTrace();
        }
    }
}
